package de.dhbw.ka.tit09.picsim;

import java.util.BitSet;

import de.dhbw.ka.tit09.picsim.model.PICModelProvider;

/**
 * The instruction manager class decodes and handles all instructions of the
 * PIC16C84 microcontroller. This class is a singleton.
 * 
 * @author Andreas Bender
 * 
 */
public class InstructionManager {
	private static final InstructionManager INSTANCE = new InstructionManager();
	private PICModelProvider picModelProvider = PICModelProvider.getInstance();

	private BitSet opCodeBitSet = new BitSet(14);
	private int opCode;

	private InstructionManager() {

	}

	/**
	 * Returns the instance of the instruction manager.
	 * 
	 * @return the manager instance
	 */
	public static InstructionManager getInstance() {
		return INSTANCE;
	}

	/**
	 * Decodes the operation code and calls a corresponding function.
	 * 
	 * @param pOpCode the operation code
	 */
	public void runOperation(int pOpCode) {
		picModelProvider.incProgramCounter();

		// mask 14-Bit
		opCode = pOpCode & 0x3FFF;

		initOpBitSet(opCode);

		if (opCodeBitSet.get(13)) {
			// 1xxxxxxxxxxxxx
			if (opCodeBitSet.get(12)) {
				// 11xxxxxxxxxxxx
				switch (opCode >>> 10 & 3) {
				case 0:
					movlw();
					return;
				case 1:
					retlw();
					return;
				case 2:
					if (opCodeBitSet.get(9)) {
						xorlw();
					} else {
						if (opCodeBitSet.get(8)) {
							andlw();
						} else {
							iorlw();
						}
					}
					return;
				case 3:
					if (opCodeBitSet.get(9)) {
						addlw();
					} else {
						sublw();
					}
					return;
				}
			} else {
				// 10xxxxxxxxxxxx
				if (opCodeBitSet.get(11)) {
					goTo();
				} else {
					call();
				}
				return;
			}
		} else {
			// 0xxxxxxxxxxxxx
			if (opCodeBitSet.get(12)) {
				// 01xxxxxxxxxxxx
				switch (opCode >>> 10 & 3) {
				case 0:
					bcf();
					return;
				case 1:
					bsf();
					return;
				case 2:
					btfsc();
					return;
				case 3:
					btfss();
					return;
				}
			} else {
				// 00xxxxxxxxxxxx
				switch (opCode >>> 8 & 15) {
				case 0:
					if (opCodeBitSet.get(7)) {
						movwf();
						return;
					} else {
						switch (opCode & 15) {
						case 0:
							nop();
							return;
						case 3:
							sleep();
							return;
						case 4:
							clrwdt();
							return;
						case 8:
							reTurn();
							return;
						case 9:
							retfie();
							return;
						}
					}
					return;
				case 1:
					if (opCodeBitSet.get(7)) {
						clrf();
					} else {
						clrw();
					}
					return;
				case 2:
					subwf();
					return;
				case 3:
					decf();
					return;
				case 4:
					iorwf();
					return;
				case 5:
					andwf();
					return;
				case 6:
					xorwf();
					return;
				case 7:
					addwf();
					return;
				case 8:
					movf();
					return;
				case 9:
					comf();
					return;
				case 10:
					incf();
					return;
				case 11:
					decfsz();
					return;
				case 12:
					rrf();
					return;
				case 13:
					rlf();
					return;
				case 14:
					swapf();
					return;
				case 15:
					incfsz();
					return;
				}
			}

		}
	}

	private void initOpBitSet(int opCode) {
		opCodeBitSet.clear();

		for (int i = 0; i < 14; i++) {
			if (opCode % 2 != 0) {
				opCodeBitSet.set(i);
			}
			opCode = opCode >>> 1;
		}
	}

	private void addlw() {
		// System.out.println("addlw");

		int result = picModelProvider.getWRegister() + (opCode & 0xFF);

		if (result > 255) {
			result = result - 256;
		}

		checkAndSet_C_DC_Z(result);
		picModelProvider.setWRegister(result);
	}

	private void addwf() {
		// System.out.println("addwf");

		int result = picModelProvider.getWRegister()
				+ picModelProvider.getRegisterAtAddress(opCode & 127);

		if (result > 255) {
			result = result - 256;
		}

		checkAndSet_C_DC_Z(result);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void andlw() {
		// System.out.println("andlw");

		int result = picModelProvider.getWRegister() & (opCode & 0xFF);
		checkAndSet_Z(result);
		picModelProvider.setWRegister(result);
	}

	private void andwf() {
		// System.out.println("andwf");

		int result = picModelProvider.getWRegister()
				& picModelProvider.getRegisterAtAddress(opCode & 127);

		checkAndSet_Z(result);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void bcf() {
		// System.out.println("bcf");

		picModelProvider.setRegisterAtAddress(opCode & 127, BitUtility
				.getBitModifiedInt(
						picModelProvider.getRegisterAtAddress(opCode & 127),
						BitUtility.getValueInRange(opCode, 7, 9), false));
	}

	private void bsf() {
		// System.out.println("bsf");
		picModelProvider.setRegisterAtAddress(opCode & 127, BitUtility
				.getBitModifiedInt(
						picModelProvider.getRegisterAtAddress(opCode & 127),
						BitUtility.getValueInRange(opCode, 7, 9), true));
	}

	private void btfsc() {
		// System.out.println("btfsc");

		if (!BitUtility.getBitValueFromInt(
				picModelProvider.getRegisterAtAddress(opCode & 127),
				BitUtility.getValueInRange(opCode, 7, 9))) {
			picModelProvider.incProgramCounter();

			// 2nd cycle
			picModelProvider.getTimers().countCycle();
		}
	}

	private void btfss() {
		// System.out.println("btfss");

		if (BitUtility.getBitValueFromInt(
				picModelProvider.getRegisterAtAddress(opCode & 127),
				BitUtility.getValueInRange(opCode, 7, 9))) {
			picModelProvider.incProgramCounter();

			// 2nd cycle
			picModelProvider.getTimers().countCycle();
		}
	}

	private void call() {
		// System.out.println("call");

		picModelProvider.getStack().push(picModelProvider.getProgramCounter());
		picModelProvider.setProgramCounter(opCode & 2047);
		// FIXME load pclath in pc. ignore in PIC16C84??
		// int pclath_3_4 = BitUtility.getValueInRange(picModelProvider
		// .getSFRegisters().getPCLATH(), 3, 4);
		// picModelProvider.setProgramCounter(picModelProvider.getProgramCounter()
		// | pclath_3_4 << 11);

		// 2nd cycle
		picModelProvider.getTimers().countCycle();
	}

	private void clrf() {
		// System.out.println("clrf");

		picModelProvider.setRegisterAtAddress(opCode & 127, 0);
		picModelProvider.getSFRegisters().setZ(true);
	}

	private void clrw() {
		// System.out.println("clrw");

		picModelProvider.setWRegister(0);
		picModelProvider.getSFRegisters().setZ(true);
	}

	private void clrwdt() {
		// System.out.println("clrwdt");
	}

	private void comf() {
		// System.out.println("comf");

		int result = (~picModelProvider.getRegisterAtAddress(opCode & 127)) & 255;

		checkAndSet_Z(result);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}

	}

	private void decf() {
		// System.out.println("decf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127) - 1;

		if (result < 0) {
			result = 0xFF;
		}

		checkAndSet_Z(result);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void decfsz() {
		// System.out.println("decfsz");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127) - 1;

		if (result < 0) {
			result = 0xFF;
		}

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}

		if (result == 0) {
			picModelProvider.incProgramCounter();

			// 2nd cycle
			picModelProvider.getTimers().countCycle();
		}
	}

	private void goTo() {
		// System.out.println("goto");

		picModelProvider.setProgramCounter(opCode & 2047);
		// FIXME pclath

		// 2nd cycle
		picModelProvider.getTimers().countCycle();
	}

	private void incf() {
		// System.out.println("incf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127) + 1;

		if (result > 0xFF) {
			result = 0;
		}

		checkAndSet_Z(result);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void incfsz() {
		// System.out.println("incfsz");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127) + 1;

		if (result > 0xFF) {
			result = 0;
		}

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}

		if (result == 0) {
			picModelProvider.incProgramCounter();

			// 2nd cycle
			picModelProvider.getTimers().countCycle();
		}
	}

	private void iorlw() {
		// System.out.println("iorlw");

		int result = picModelProvider.getWRegister() | (opCode & 255);
		checkAndSet_Z(result);// FIXME when z is set?
		picModelProvider.setWRegister(result);
	}

	private void iorwf() {
		// System.out.println("iorwf");

		int result = picModelProvider.getWRegister()
				| picModelProvider.getRegisterAtAddress(opCode & 127);

		checkAndSet_Z(result);// FIXME when z is set?

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void movf() {
		// System.out.println("movf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127);

		checkAndSet_Z(result);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void movlw() {
		// System.out.println("movlw");

		picModelProvider.setWRegister(opCode & 255);
	}

	private void movwf() {
		// System.out.println("movwf");

		picModelProvider.setRegisterAtAddress(opCode & 127,
				picModelProvider.getWRegister());
	}

	private void nop() {
		// System.out.println("nop");
	}

	private void retfie() {
		// System.out.println("retfie");

		picModelProvider.setProgramCounter(picModelProvider.getStack().pop());
		picModelProvider.getSFRegisters().setINTCON(
				BitUtility.getBitModifiedInt(picModelProvider.getSFRegisters()
						.getINTCON(), 7, true));

		// 2nd cycle
		picModelProvider.getTimers().countCycle();
	}

	private void retlw() {
		// System.out.println("retlw");

		picModelProvider.setWRegister(opCode & 255);
		picModelProvider.setProgramCounter(picModelProvider.getStack().pop());

		// 2nd cycle
		picModelProvider.getTimers().countCycle();
	}

	private void reTurn() {
		// System.out.println("return");

		picModelProvider.setProgramCounter(picModelProvider.getStack().pop());

		// 2nd cycle
		picModelProvider.getTimers().countCycle();
	}

	private void rlf() {
		// System.out.println("rlf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127);

		boolean c = picModelProvider.getSFRegisters().getC();
		picModelProvider.getSFRegisters().setC(opCodeBitSet.get(6));

		result = (result << 1) & 255;

		if (c) {
			result = (result + 1);
		}

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void rrf() {
		// System.out.println("rrf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127);

		boolean c = picModelProvider.getSFRegisters().getC();
		picModelProvider.getSFRegisters().setC(opCodeBitSet.get(0));

		result = result >>> 1;

		if (c) {
			result = BitUtility.getBitModifiedInt(result, 7, true);
		}

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void sleep() {
		// System.out.println("sleep");
	}

	private void sublw() {
		// System.out.println("sublw");

		int result = opCode & 255;
		int operand = picModelProvider.getWRegister();
		result = result - operand;

		if (result < 0) {
			result = 255 + (result + 1);
			picModelProvider.getSFRegisters().setC(false);
		} else {
			picModelProvider.getSFRegisters().setC(true);
		}

		checkAndSet_Z(result);

		if (result > 0xF) {
			picModelProvider.getSFRegisters().setDC(true);
		} else {
			picModelProvider.getSFRegisters().setDC(false);// FIXME correct
															// check??
		}

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}

	}

	private void subwf() {
		// System.out.println("subwf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 255);
		int operand = picModelProvider.getWRegister();
		result = result - operand;

		if (result < 0) {
			result = 255 + (result + 1);
			picModelProvider.getSFRegisters().setC(false);
		} else {
			picModelProvider.getSFRegisters().setC(true);
		}

		checkAndSet_Z(result);

		if (result > 0xF) {
			picModelProvider.getSFRegisters().setDC(true);
		} else {
			picModelProvider.getSFRegisters().setDC(false);// FIXME correct
															// check??
		}

		picModelProvider.setWRegister(result);
	}

	private void swapf() {
		// System.out.println("swapf");

		int result = picModelProvider.getRegisterAtAddress(opCode & 127);
		int lowNibble = result & 15;

		result = result >>> 4;
		result = result | (lowNibble << 4);

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void xorlw() {
		// System.out.println("xorlw");

		int result = picModelProvider.getWRegister() ^ (opCode & 255);
		checkAndSet_Z(result);// FIXME when z is set?
		picModelProvider.setWRegister(result);
	}

	private void xorwf() {
		// System.out.println("xorwf");

		int result = picModelProvider.getWRegister()
				^ picModelProvider.getRegisterAtAddress(opCode & 127);

		checkAndSet_Z(result);// FIXME when z is set?

		if (opCodeBitSet.get(7)) {
			picModelProvider.setRegisterAtAddress(opCode & 127, result);
		} else {
			picModelProvider.setWRegister(result);
		}
	}

	private void checkAndSet_C_DC_Z(int result) {
		checkAndSet_Z(result);

		if (result > 0xF) {
			picModelProvider.getSFRegisters().setDC(true);

			if (result > 0xFF) {
				picModelProvider.getSFRegisters().setC(true);
			}
		} else {
			picModelProvider.getSFRegisters().setDC(false);
			picModelProvider.getSFRegisters().setC(false);
		}
	}

	private void checkAndSet_Z(int result) {
		if (result == 0) {
			picModelProvider.getSFRegisters().setZ(true);
		} else {
			picModelProvider.getSFRegisters().setZ(false);
		}
	}

}
